package com.atguigu.gulimall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.mq.SeckillOrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberRespVo;
import com.atguigu.gulimall.seckill.interceptor.LoginUserInterceptor;
import com.atguigu.gulimall.seckill.to.SecKillSessionWithSkus;
import com.atguigu.gulimall.seckill.to.SeckillSkuRelationTo;
import com.atguigu.gulimall.seckill.to.SkuInfoTo;
import com.atguigu.gulimall.seckill.to.SkuSecKillCahceTo;
import com.atguigu.gulimall.seckill.feign.CouponFeignService;
import com.atguigu.gulimall.seckill.feign.ProductFeignService;
import com.atguigu.gulimall.seckill.service.SecKillService;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.xml.crypto.Data;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class SecSkillServiceImpl implements SecKillService {

    private final String SESSION_CACHE_PREFIX = "seckill:session:";

    private final String SKUKILL_CACHE_PREFIX ="seckill:skus";

    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";

    @Autowired
    private CouponFeignService couponFeignService;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void uploadSeckillLasted3Days() {
        R r = couponFeignService.getLates3DaysSession();
        if (r.getCode() == 0){
            List<SecKillSessionWithSkus> secKillSessionWithSkus = r.getData(new TypeReference<List<SecKillSessionWithSkus>>() {});
            saveSessionInfos(secKillSessionWithSkus);
            saveSessionSkuInfos(secKillSessionWithSkus);
        }
    }

    @Override
    public List<SkuSecKillCahceTo> getCurrentSecKillSkus() {
        long curTime = new Date().getTime();
        Set<String> keys = redisTemplate.keys(SESSION_CACHE_PREFIX + "*");

        for (String key : keys) {
            String replace = key.replace(SESSION_CACHE_PREFIX, "");
            String[] s = replace.split("_");
            long start = Long.parseLong(s[0]);
            long end = Long.parseLong(s[1]);
            if (curTime>= start && curTime<end){
                Long size = redisTemplate.opsForList().size(key);
                List<String> range = redisTemplate.opsForList().range(key, 0, size - 1);
                BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> curSessionSkusStr = ops.multiGet(range);
                if (curSessionSkusStr!=null && curSessionSkusStr.size()>0){
                    List<SkuSecKillCahceTo> collect = curSessionSkusStr.stream().map(skuSecKillJson -> {
                        return JSON.parseObject(skuSecKillJson, SkuSecKillCahceTo.class);
                    }).collect(Collectors.toList());
                    return collect;
                }
                break;
            }
        }
        return null;
    }

    @Override
    public SkuSecKillCahceTo getSkuSeckillInfo(Long skuId) {
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        Set<String> keys = ops.keys();
        if (keys!=null&&keys.size()>0){
            String regx = "\\d_"+skuId;
            for (String key : keys) {
                boolean isMatch = Pattern.matches(regx, key);
                if (isMatch){
                    String json = ops.get(key);
                    SkuSecKillCahceTo skuSecKillCahceTo = JSON.parseObject(json, SkuSecKillCahceTo.class);
                    Long endTime = skuSecKillCahceTo.getEndTime();
                    long nowTime = new Date().getTime();
                    if (nowTime<endTime){
                        Long startTime = skuSecKillCahceTo.getStartTime();
                        if (nowTime<startTime){
                            skuSecKillCahceTo.setRandomCode(null);
                        }
                        return skuSecKillCahceTo;
                    }
                }
            }
        }
        return null;
    }

    @Override
    public String kill(String killId, String code, Integer num) {
        //1.获取
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        String json = ops.get(killId);
        if (!StringUtils.isEmpty(json)){
            SkuSecKillCahceTo skuSecKillCahceTo = JSON.parseObject(json, SkuSecKillCahceTo.class);
            Long startTime = skuSecKillCahceTo.getStartTime();
            Long endTime = skuSecKillCahceTo.getEndTime();
            long curTime = new Date().getTime();
            if (curTime>=startTime&&curTime<endTime){
                String randomCode = skuSecKillCahceTo.getRandomCode();
                Long skuId = skuSecKillCahceTo.getSkuId();
                if (randomCode.equals(code)){
                    if (num <= skuSecKillCahceTo.getSeckillLimit().intValue()){
                        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
                        String userKill = memberRespVo.getId()+"_"+ killId;
                        Boolean isSet = redisTemplate.opsForValue().setIfAbsent(userKill, num.toString(), endTime - curTime, TimeUnit.MILLISECONDS);
                        if (isSet){
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                            boolean isAcquire = semaphore.tryAcquire(num);
                            //秒杀成功
                            if (isAcquire){
                                SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
                                String timeId = IdWorker.getTimeId();
                                seckillOrderTo.setOrderSn(timeId);
                                seckillOrderTo.setKillId(killId);
                                seckillOrderTo.setSeckillPrice(skuSecKillCahceTo.getSeckillPrice());
                                seckillOrderTo.setNum(num);
                                seckillOrderTo.setMemberId(memberRespVo.getId());
                                rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",seckillOrderTo);
                                return timeId;
                            }
                        }
                    }
                }
            }
        }

        return null;
    }

    private void saveSessionSkuInfos(List<SecKillSessionWithSkus> secKillSessionWithSkus) {
        if (secKillSessionWithSkus!=null){
            secKillSessionWithSkus.forEach(session->{
                List<SeckillSkuRelationTo> relationSkus = session.getRelationSkus();
                if (relationSkus!=null){
                    BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                    relationSkus.forEach(r->{
                        SkuSecKillCahceTo skuSecKillCahceTo = new SkuSecKillCahceTo();
                        BeanUtils.copyProperties(r,skuSecKillCahceTo);
                        String key = r.getPromotionSessionId() +"_"+r.getSkuId();
                        Boolean hasKey = ops.hasKey(key);
                        if (hasKey) return;

                        R res = productFeignService.info(r.getSkuId());
                        if (res.getCode() == 0){
                            SkuInfoTo skuInfoTo = res.getData("skuInfo", new TypeReference<SkuInfoTo>() {});
                            skuSecKillCahceTo.setSkuInfoTo(skuInfoTo);
                        }
                        skuSecKillCahceTo.setStartTime(session.getStartTime().getTime());
                        skuSecKillCahceTo.setEndTime(session.getEndTime().getTime());
                        String randomCode = UUID.randomUUID().toString().replace("-", "");
                        skuSecKillCahceTo.setRandomCode(randomCode);

                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                        semaphore.trySetPermits(r.getSeckillCount().intValue());

                        ops.putIfAbsent(key, JSON.toJSONString(skuSecKillCahceTo));
                    });
                }
            });
        }


    }

    private void saveSessionInfos(List<SecKillSessionWithSkus> secKillSessionWithSkus) {
        if (secKillSessionWithSkus!=null){
            secKillSessionWithSkus.forEach(session->{
                long startTime = session.getStartTime().getTime();
                long endTime = session.getEndTime().getTime();
                String key = startTime +"_"+ endTime;
                Boolean hasKey = redisTemplate.hasKey(SESSION_CACHE_PREFIX + key);
                if (hasKey) return;
                List<SeckillSkuRelationTo> relationSkus = session.getRelationSkus();
                Long sessionId = session.getId();
                if (relationSkus!=null){
                    List<String> collect = relationSkus.stream().map(r -> sessionId + "_" + r.getSkuId()).collect(Collectors.toList());

                    redisTemplate.opsForList().leftPushAll(SESSION_CACHE_PREFIX + key, collect);
                }
            });
        }
    }
}
